// @ts-nocheck
import dayjs from 'dayjs'
import utc from 'dayjs/plugin/utc.js'
import timezone from 'dayjs/plugin/timezone.js'
import arraySupport from 'dayjs/plugin/arraySupport.js'
import toArray from 'dayjs/plugin/toArray.js'
import isoWeek from 'dayjs/plugin/isoWeek.js'

dayjs.extend(utc)
dayjs.extend(timezone)
dayjs.extend(arraySupport)
dayjs.extend(toArray)
dayjs.extend(isoWeek)

/**
 * @typedef {number} TimeStampMS 时间戳, 单位ms
 * @typedef {number} TimeStampS 时间戳, 单位s
 * @typedef {TimeStampMS | Date} Datetime  日期时间, 如：时间戳、时间对象
 * @typedef {import('dayjs').Dayjs} Dayjs
 * @typedef {[number, number, number, number, number, number, number]} TimeArray 时间数组
 * @typedef {number | Date | string | Time | Dayjs | undefined} CreateDayjsValue
 */

/** 全局时区名称 */
let globalTimeZoneName = dayjs.tz.guess()

/**
 * 创建dayjs对象
 * @param {CreateDayjsValue?} value 
 * @returns {Dayjs}
 */
function createDayjs(value) {
  let d
  if (value instanceof Time) {
    d = value.raw.unix() * 1000
  } else if (dayjs.isDayjs(value)) {
    d = value.unix() * 1000
  } else if (value instanceof Date) {
    d = value.getTime()
  } else if (typeof value === 'number') {
    d = new Date(value).getTime()
  } else if (typeof value === 'undefined') {
    // d = dayjs()
  } else if (typeof value === 'string') {
    d = new Date(value).getTime()
  } else {
    throw new Error(`类型错误 ${typeof value}`)
  }
  const baseDj = dayjs(d)
  // if (!baseDj.isValid()) throw new Error('生成Time实例无效')
  const dj = resolveTimeZoneDayjs(baseDj)
  return dj
}
/**
 * 确定正确时区的Dayjs
 * @param {dayjs.Dayjs} baseDj 
 * @param {string?} timeZoneName 
 * @returns 
 */
function resolveTimeZoneDayjs(baseDj, timeZoneName) {
  const timeZoneNameDj = baseDj.tz(timeZoneName)
  if (timeZoneNameDj.isUTC()) {
    return timeZoneNameDj.utc()
  }
  return timeZoneNameDj
}
/**
 * 格式化时区名称
 * @param {(string | number)?} value 
 * @returns {string}
 */
function formatTimeZoneName(value) {
  let timeZoneName = ''
  if (value === undefined || value === null) {
    timeZoneName = dayjs.tz.guess()
  } else if (typeof value === 'string') {
    timeZoneName = value
  } else if (typeof value === 'number') {
    const hour = `${Math.floor(Math.abs(value) / 60 / 60)}`.padStart(2, '0')
    const milli = `${Math.floor(Math.abs(value) / 60 % 60)}`.padStart(2, '0')
    const unit = value >= 0 ? '+' : '-'
    const name = `${unit}${hour}${milli}`
    timeZoneName = name
  } else {
    throw new Error('解析时区参数错误')
  }
  return timeZoneName
}
/**
 * 不是数值就抛出错误
 * @param {any} value 
 * @returns {boolean}
 */
function ifNotNumberThrow(value) {
  if (typeof value === 'number') {
    return
  } else {
    throw new Error('参数不是数值')
  }
}
/**
 * 通过时间戳生成客户端时间
 * @param {number} timeStamp 
 * @returns {Time}
 */
export function timeStampToClientTime(timeStamp) {
  if (typeof timeStamp !== 'number') throw new Error('参数类型不是数值')
  return new Time(Math.floor(timeStamp * 1000))
}
/**
 * 把客户端时间转换为时间戳
 * @param {Time} clientTime 
 * @returns {number}
 */
export function clientTimeToTimeStamp(clientTime) {
  if (!(clientTime instanceof Time)) throw new Error('参数类型不是Time')
  return clientTime.toTimestamp()
}
/** 
 * 把客户端时间转换为本地时间选择器使用的时间
 * @param {[CreateDayjsValue, CreateDayjsValue]} originTimeRange 时间 ms
 * @return {[Date, Date]} 选择器时间
 * */
export function clientTimeToLocalDateTimePickerRange(originTimeRange) {
  if (originTimeRange.length !== 2) throw new Error('参数类型不是长度为2的数组')
  const [start, end] = originTimeRange
  return [
    Time.clientTimeToLocalDateTimePicker(start),
    Time.clientTimeToLocalDateTimePicker(end),
  ]
}

/**
 * 把本地时间选择器使用的时间转换为客户端时间 修正时区
 * @param {[Date, Date]} originTimeRange 选择器时间
 * @return {[Time, Time]} 时间 ms
 */
export function localDateTimePickerToClientTimeRange(originTimeRange) {
  if (originTimeRange.length !== 2) throw new Error('参数类型不是长度为2的数组')
  const [start, end] = originTimeRange
  return [
    Time.localDateTimePickerToClientTime(start),
    Time.localDateTimePickerToClientTime(end),
  ]
}

export class Time {
  /**
   * 设置全局时区
   * @param {(string | number)?} value 
   */
  static setGlobalTimeZoneName(value) {
    globalTimeZoneName = formatTimeZoneName(value)
    dayjs.tz.setDefault(globalTimeZoneName)
  }
  /**
   * 获取时区描述
   * @returns {string}
   */
  static getGlobalTimeZoneName() {
    return globalTimeZoneName
  }
  /**
   * 新本地时间
   * @param {number | Date | Time | Dayjs} originTime
   * @param {number} year
   * @param {number} month 从1开始
   * @param {number} date
   * @param {number} hours
   * @param {number} min
   * @param {number} sec
   * @param {number} ms
   * @return {Time} 时间对象
   */
  static newTime(originTime, year, month, date, hours, min, sec, ms) {
    const originTimeArray = createDayjs(originTime).toArray()
    const newTimeArray = [year, typeof month === 'number' ? month - 1 : month, date, hours, min, sec, ms]
    .map((v, i) => (v === undefined || v === null) ? originTimeArray[i] : v)
    const ks = ['setYear', 'setMonth', 'setDate', 'setHour', 'setMinute', 'setSecond', 'setMillisecond']
    const newTime = new Time()
    ks.reduce((to, k, i) => to[k](newTimeArray[i]), newTime)
    return newTime
  }
  /**
   * 把客户端时间转换为本地时间选择器使用的时间 修正时区
   * @param {number | Date | Time | Dayjs} originTime 时间 ms
   * @return {Date} 选择器时间
   */
  static clientTimeToLocalDateTimePicker(originTime) {
    const c = createDayjs(originTime)
    const cOffset = c.utcOffset() * 60
    const l = dayjs(c.unix() * 1000)
    const lOffset = l.utcOffset() * 60
    return new Date((l.unix() - lOffset + cOffset) * 1000)
  }
   /**
   * 把本地时间选择器使用的时间转换为客户端时间 修正时区
   * @param {Date} dtpTime 选择器时间
   * @return {Time}
   */
  static localDateTimePickerToClientTime(dtpTime) {
    const c = createDayjs(dtpTime)
    const cOffset = c.utcOffset() * 60
    const l = dayjs(Math.floor(dtpTime.getTime() / 1000))
    const lOffset = l.utcOffset() * 60
    return new Time((Math.floor(dtpTime.getTime() / 1000) + lOffset - cOffset) * 1000)
  }

  /** 内部原始时间对象 */
  get raw() {return this._raw}
  /** @type {Dayjs} @private */
  _raw = undefined
  /** 时区名称 @type {string} @private */
  _timeZoneName = globalTimeZoneName
  /**
   * 
   * @param {CreateDayjsValue?} value
   * @param {string?} timeZoneName 
   */
  constructor(value?: any, timeZoneName?: any) {
    this._raw = createDayjs(value)
    if (timeZoneName) {
      this.setTimeZoneName(timeZoneName)
    }
  }
  /**
   * 获取年份
   * @returns {number}
   */
  getYear() {
    return this._raw.get('year')
  }
  getFullYear() {return this.getYear()}
  /**
   * 设置年份
   * @param {number} value 
   * @returns {Time}
   */
  setYear(value) {
    ifNotNumberThrow(value)
    this._raw = this._raw.set('year', value)
    return this
  }
  setFullYear(value) {return this.setYear(value)}
  /**
   * 获取月份 从0开始
   * @returns {number}
   */
  getMonth() {
    return this._raw.get('month')
  }
  /**
   * 设置月份 从0开始
   * @param {number} value 
   * @returns {Time}
   */
  setMonth(value) {
    ifNotNumberThrow(value)
    this._raw = this._raw.set('month', value)
    return this
  }
  /**
   * 获取日期 从1开始
   * @returns {number}
   */
  getDate() {
    return this._raw.get('date')
  }
  /**
   * 设置日期 从1开始
   * @param {number} value 
   * @returns {Time}
   */
  setDate(value) {
    ifNotNumberThrow(value)
    this._raw = this._raw.set('date', value)
    return this
  }
  /**
   * 获取星期 从1开始
   * @returns {number}
   */
  getDay() {
    return this._raw.get('day')
  }
  /**
   * 设置星期 从1开始
   * @param {number} value 
   * @returns {Time}
   */
  setDay(value) {
    ifNotNumberThrow(value)
    this._raw = this._raw.set('day', value)
    return this
  }
  /**
   * 获取小时 从0开始
   * @returns {number}
   */
  getHour() {
    return this._raw.get('hour')
  }
  getHours() {return this.getHour()}
  /**
   * 设置小时 从0开始
   * @param {number} value 
   * @returns {Time}
   */
  setHour(value) {
    ifNotNumberThrow(value)
    this._raw = this._raw.set('hour', value)
    return this
  }
  setHours(value) {return this.setHour(value)}
  /**
   * 获取分钟 从0开始
   * @returns {number}
   */
  getMinute() {
    return this._raw.get('minute')
  }
  getMinutes() {return this.getMinute()}
  /**
   * 设置分钟 从0开始
   * @param {number} value 
   * @returns {Time}
   */
  setMinute(value) {
    ifNotNumberThrow(value)
    this._raw = this._raw.set('minute', value)
    return this
  }
  setMinutes(value) {return this.setMinute(value)}
  /**
   * 获取秒 从0开始
   * @returns {number} 
   */
  getSecond() {
    return this._raw.get('second')
  }
  getSeconds() {return this.getSecond()}
  /**
   * 设置秒 从0开始
   * @param {number} value 
   * @returns {Time}
   */
  setSecond(value) {
    ifNotNumberThrow(value)
    this._raw = this._raw.set('second', value)
    return this
  }
  setSeconds() {return this.setSecond(value)}
  /**
   * 获取毫秒 从0开始
   * @returns {number}
  */
  getMillisecond() {
    return this._raw.get('millisecond')
  }
  getMilliseconds() {return this.getMillisecond()}
  /**
   * 设置毫秒 从0开始
   * @param {number} value
   * @returns {Time}
   */
  setMillisecond(value) {
    ifNotNumberThrow(value)
    this._raw = this._raw.set('millisecond', value)
    return this
  }
  setMilliseconds(value) {return this.setMillisecond(value)}
  /**
   * 格式化
   * @param {string} template 
   * @returns {string}
   */
  format(template = 'YYYY-MM-DD HH:mm:ss') {
    return this._raw.format(template)
  }
  timeFormat(template = 'YYYY-MM-DD HH:mm:ss') {
    return this._raw.format(template)
  }
  /**
   * 获取时区名称
   * @returns {string}
   */
  getTimeZoneName() {
    return this._timeZoneName
  }
  /**
   * 设置时区名称
   * @param {(string | number)?} value 
   */
  setTimeZoneName(value) {
    this._timeZoneName = formatTimeZoneName(value)
    this._raw = resolveTimeZoneDayjs(this._raw, this._timeZoneName)
    return this
  }
  /**
   * 获取时区偏移量
   * @returns {number} 单位分钟
   */
  getTimeZoneOffset() {
    return this._raw.utcOffset()
  }
  /**
   * 获取时区偏移量
   * @returns {number} 单位秒
   */
  getTimeZoneOffsetM() {
    return this._raw.utcOffset()*60
  }
  /**
   * 获取特殊时间对象
   * @returns {SpecialTime}
   */
  getSpecialTime() {
    return new SpecialTime(this)
  }
  /**
   * 转换为Date对象
   * @returns {Date}
  */
  toDate() {
    return this._raw.toDate()
  }
  /**
   * 转换为时间戳
   * @returns {number} 单位s
  */
  toTimestamp() {
    return this._raw.unix()
  }
  /**
   * 转换为时间戳
   * @returns {number} 单位ms
  */
  toTimestampMS() {
    return Math.floor(this._raw.unix() * 1000)
  }
  /** 
   * 转换为数组
   * @returns {TimeArray}
   */
  toArray() {
    return this._raw.toArray()
  }
  /**
   * 转换为字符串
   * @returns {string}
   */
  toString() {
    return this._raw.toString()
  }
}

class SpecialTime {
  /** @type {Time} @private */
  _time = undefined
  /**
   * 
   * @param {Time} time 
   */
  constructor(time) {
    if (!(time instanceof Time)) throw new Error('类型错误')
    this._time = time
  }
  /**
   * 获取当前小时
   * @return {[Time, Time]}
   * */
  getHour() {
    return [
      Time.newTime(this._time, null, null, null, null, 0, 0, 0),
      Time.newTime(this._time, null, null, null, null, 59, 59, 0),
    ]
  }
  /**
   * 获取今天
   * @return {[Time, Time]}
   * */
  getToday() {
    return [
      Time.newTime(this._time, null, null, null, 0, 0, 0, 0),
      Time.newTime(this._time, null, null, null, 23, 59, 59, 0),
    ]
  }
  /** 
   * 获取昨天
   * @return {[Time, Time]} 
   * */
  getYesterday() {
    return [
      Time.newTime(this._time, null, null, this._time.getDate() - 1, 0, 0, 0, 0),
      Time.newTime(this._time, null, null, this._time.getDate() - 1, 23, 59, 59, 0),
    ]
  }
  /** 
   * 获取本周
   * @return {[Time, Time]} 
   * */
  getThisWeek() {
    const currentWeekDay = this._time.getDay() || 7
    const start = Time.newTime(this._time, null, null, this._time.getDate() - currentWeekDay + 1, 0, 0, 0, 0)
    const end = Time.newTime(start, null, null, start.getDate() + 6, 23, 59, 59, 0)
    return [start, end]
  }
  /**
   * 获取上周
   * @return {[Time, Time]} 
   * */
  getLastWeek() {
    const currentWeekDay = this._time.getDay() || 7
    const end = Time.newTime(this._time, null, null, this._time.getDate() - currentWeekDay, 23, 59, 59, 0)
    const start = Time.newTime(end, null, null, end.getDate() - 6, 0, 0, 0, 0)
    return [start, end]
  }
  /**
   * 获取本月
   * @return {[Time, Time]} 
   * */
  getThisMonth() {
    const start = Time.newTime(this._time, null, null, 1, 0, 0, 0, 0)
    const end = Time.newTime(this._time, null, start.getMonth() + 2, 0, 23, 59, 59, 0)
    return [start, end]
  }
  /**
   * 获取上月
   * @return {[Time, Time]} 
   * */
  getLastMonth() {
    const start = Time.newTime(this._time, null, this._time.getMonth() - 1 + 1, 1, 0, 0, 0, 0)
    const end = Time.newTime(this._time, null, this._time.getMonth() - 1 + 2, 0, 23, 59, 59, 0)
    return [start, end]
  }
  /**
   * 获取从当前时间往前推一周
   * @return {[Time, Time]}
  */
  getLatestWeek() {
    let start = this._time._raw.add(1, 'day').startOf('day')
    start = start.subtract(7, 'day')
    let end = this._time._raw.endOf('day')
    return [new Time(start), new Time(end)]
  }
  /** 
   * 获取从当前时间往前推一个月
   * @return {[Time, Time]} 
   * */
  getLatestMonth() {
    let start = this._time._raw.add(1, 'day').startOf('day')
    start = start.subtract(1, 'month')
    let end = this._time._raw.endOf('day')
    return [new Time(start), new Time(end)]
  }
  /** 
   * 获取从当前时间往前推三个月
   * @return {[Time, Time]} 
   * */
  getLatestThreeMonth() {
    let start = this._time._raw.add(1, 'day').startOf('day')
    start = start.subtract(3, 'month')
    let end = this._time._raw.endOf('day')
    return [new Time(start), new Time(end)]
  }
}

/**
 * 获取时间节点序列范围
 * 
 * @param {Array<{x: Date, y: string | number}>} data 
 * @param {Array<{start: Date, end: Date, label: string}>} configNodes 
 * @returns {Array<{x: string, y: string | number}>}
 */
export function getTimeNodes(data, configNodes) {
  const options = []
  for (let i = 0; i < configNodes.length;) {
    const node = configNodes[i]
    const x = node.label ?? i
    let isMatch = false
    for (let j = 0; j < data.length; ++j) {
      const item = data[j]
      if (node.start <= item.x && node.end > item.x) {
        options.push({x, y: item.y})
        data.splice(j, 1)
        isMatch = true
        break
      }
    }
    if (!isMatch) {
      options.push({x, y: 0})
    }
    ++i
  }
  return options
}
const TIME_NODE_TYPE = {
  HOUR: 1,
  DAY: 2,
  WEEK: 3,
  MONTH: 4,
}
export class TimeNodes {
  static TIME_NODE_TYPE = TIME_NODE_TYPE
  /** @private @type {[{x: Date, y: number}, {x: Date, y: number}]} */
  _date = []
  /** @private @type {[Date | Time, Date | Time]} */
  _datetimeRange = []
  /**
   * 
   * @param {[{x: Date, y: number}, {x: Date, y: number}]} data 
   * @param {[Date | Time, Date | Time]} datetimeRange 
   */
  constructor(data, datetimeRange) {
    this._date = data
    this._datetimeRange = datetimeRange
  }
  render(timeNodeType) {
    if (!timeNodeType) throw new Error('时间节点类型错误')
    if (this._datetimeRange.length) {
      /** @type {Array<{label: string; start: Date; end: Date;}>} */
      let configNodes
      switch (timeNodeType) {
        case TIME_NODE_TYPE.HOUR: {
          configNodes = createConfigHourNodes(this._datetimeRange)
        } break
        case TIME_NODE_TYPE.DAY: {
          configNodes = createConfigDayNodes(this._datetimeRange)
        } break
        case TIME_NODE_TYPE.WEEK: {
          configNodes = createConfigWeekNodes(this._datetimeRange)
        } break
        case TIME_NODE_TYPE.MONTH: {
          configNodes = createConfigMonthNodes(this._datetimeRange)
        } break
        default: throw new Error('不支持的日期时间处理类型')
      }
      const configTimeNode = getTimeNodes(this._date, configNodes)
      return configTimeNode
    } else {
      return []
    }
  }
}
/**
 * 创建已小时配置的节点
 * @param {[CreateDayjsValue, CreateDayjsValue]} dateRange 
 * @returns {Array<{label: string; start: Date; end: Date;}>}
 */
function createConfigHourNodes(dateRange) {
  const [start, end] = dateRange.map((v, i) => new Time(v).getSpecialTime().getHour()[i])
  const num = (end.raw.diff(start.raw, 'hour') + 1)
  const configNodes = new Array(num).fill(0).map((_, i) => {
    const st = Time.newTime(start, null, null, null, start.getHour() + 1 * i)
    const en = new Time(st).getSpecialTime().getHour()[1]
    return {
      label: `${st.format('HH')}`,
      start: st.toDate(), 
      end: en.toDate()
    }
  })
  return configNodes
}
/**
 * 创建已天配置的节点
 * @param {[CreateDayjsValue, CreateDayjsValue]} dateRange 
 * @returns {Array<{label: string; start: Date; end: Date;}>}
 */
function createConfigDayNodes(dateRange) {
  const [start, end] = dateRange.map((v, i) => new Time(v).getSpecialTime().getToday()[0])
  const num = (end.raw.diff(start.raw, 'day') + 1)
  const configNodes = new Array(num).fill(0).map((_, i) => {
    const st = Time.newTime(start, null, null, start.getDate() + 1 * i)
    const en = new Time(st).getSpecialTime().getToday()[1]
    return {
      label: `${st.format('YYYY-MM-DD')}`,
      start: st.toDate(), 
      end: en.toDate()
    }
  })
  return configNodes
}
/**
 * 创建已周配置的节点
 * @param {[CreateDayjsValue, CreateDayjsValue]} dateRange 
 * @returns {Array<{label: string; start: Date; end: Date;}>}
 */
function createConfigWeekNodes(dateRange) {
  const [start, end] = dateRange.map((v, i) => new Time(v).getSpecialTime().getThisWeek()[i])
  const num = Math.floor((end.raw.diff(start.raw, 'day') + 1) / 7)
  const configNodes = new Array(num).fill(0).map((_, i) => {
    const st = Time.newTime(start, null, null, start.getDate() + 7 * i)
    const en = new Time(st).getSpecialTime().getThisWeek()[1]
    return {
      label: `${st.format('YYYY-MM-DD')}~${en.format('YYYY-MM-DD')}`,
      start: st.toDate(), 
      end: en.toDate()
    }
  })
  return configNodes
}
/**
 * 创建已月配置的节点
 * @param {[CreateDayjsValue, CreateDayjsValue]} dateRange 
 * @returns {Array<{label: string; start: Date; end: Date;}>}
 */
function createConfigMonthNodes(dateRange) {
  const [start, end] = dateRange.map((v, i) => new Time(v).getSpecialTime().getThisMonth()[i])
  const num = (end.raw.diff(start.raw, 'month') + 1)
  const configNodes = new Array(num).fill(0).map((_, i) => {
    const st = Time.newTime(start, null, start.getMonth() + i + 1)
    const en = new Time(st).getSpecialTime().getThisMonth()[1]
    return {
      label: `${st.format('YYYY-MM')}`,
      start: st.toDate(), 
      end: en.toDate()
    }
  })
  return configNodes
}